﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Windows.Data;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
using System.Collections.Specialized;

using PasswordProvider.ViewModel.Properties;
using PasswordProvider.ObjectModel;
using PasswordProvider.ViewModel.Helpers;
using PasswordProvider.ViewModel.Framework;
using PasswordProvider.ViewModel.Service;
using System.Globalization;
using System.Collections;

namespace PasswordProvider.ViewModel
{
    /// <summary>
    /// View model for the sites list view which displays the list of sites.
    /// </summary>
    public class SitesListViewModel : VisualViewModel
    {
        #region Fields and Construction

        private CollectionViewSource _collectionViewSource;

        internal SitesListViewModel()
        {
            // Set up event handler for the settings property changed event.
            Settings.Default.PropertyChanged += 
                new PropertyChangedEventHandler(Settings_PropertyChanged);

            PasswordProviderData.CurrentChanged += new EventHandler(PasswordProviderData_CurrentChanged);
        }

        #endregion // Fields and Construction

        #region Public Properties

        /// <summary>
        /// Name of the current group, if showing groups. This property is set via a binding created in the 
        /// SitesListViewPage view.
        /// </summary>
        public string CurrentGroupName 
        {
            get { return _currentGroupName; }
            set
            {
                _currentGroupName = value;
                DeleteTagCommand.DisplayName = String.IsNullOrEmpty(_currentGroupName) ?
                    Strings.SiteList_DeleteEmptyTag_DisplayString : 
                    String.Format(CultureInfo.CurrentCulture, Strings.SiteList_DeleteTag_DisplayString, value);
                SetDeleteTagCommandVisibility();
            }
        } private string _currentGroupName; 

        /// <summary>
        /// The items source for the list view.
        /// </summary>
        public ICollectionView ItemsSource
        {
            get
            {
                if (_collectionViewSource == null)
                {
                    _collectionViewSource = CreateCollectionViewSource();
                }
                return _collectionViewSource.View;
            }
        }
        
        /// <summary>
        /// Show site properties command.
        /// </summary>
        public CommandViewModel ShowSitePropertiesCommand
        {
            get
            {
                if (_showSitePropertiesCommand == null)
                {
                    _showSitePropertiesCommand = new CommandViewModel(
                        Strings.SiteList_ShowSiteProperties_DisplayString,
                        Strings.SiteList_ShowSiteProperties_ToolTip,
                        new RelayCommand(
                            param => ShowSitePropertiesCommandExecuted(), 
                            param => ShowSitePropertiesCommandCanExecute()));
                }
                return _showSitePropertiesCommand;
            }
        } private CommandViewModel _showSitePropertiesCommand;

        /// <summary>
        /// Create new login site command
        /// </summary>
        public CommandViewModel CreateNewLogOnSiteCommand
        {
            get
            {
                if (_createNewLogOnSiteCommand == null)
                {
                    _createNewLogOnSiteCommand = new CommandViewModel(
                        Strings.SiteList_CreateNewLogOnSitePage_DisplayString,
                        Strings.SiteList_CreateNewLogOnSitePage_ToolTip,
                        new RelayCommand(param => CreateNewLogOnSiteCommandExecuted()));
                }
                return _createNewLogOnSiteCommand;
            }
        } private CommandViewModel _createNewLogOnSiteCommand;

        /// <summary>
        /// Create new credit card command
        /// </summary>
        public CommandViewModel CreateNewCreditCardCommand
        {
            get
            {
                if (_createNewCreditCardCommand == null)
                {
                    _createNewCreditCardCommand = new CommandViewModel(
                        Strings.SiteList_CreateNewCreditCard_DisplayString,
                        Strings.SiteList_CreateNewCreditCard_ToolTip,
                        new RelayCommand(param => CreateNewCreditCardCommandExecuted()));                    
                }
                return _createNewCreditCardCommand;
            }
        } private CommandViewModel _createNewCreditCardCommand;

        /// <summary>
        /// Create new credit card command
        /// </summary>
        public CommandViewModel CreateNewGenericSiteCommand
        {
            get
            {
                if (_createNewGenericCommand == null)
                {
                    _createNewGenericCommand = new CommandViewModel(
                        Strings.SiteList_CreateNewGeneric_DisplayName,
                        Strings.SiteList_CreateNewGeneric_ToolTip,
                        new RelayCommand(param => CreateNewGenericSiteCommandExecuted()));
                }
                return _createNewGenericCommand;
            }
        } private CommandViewModel _createNewGenericCommand;
        
        /// <summary>
        /// Copy command
        /// </summary>
        public CommandViewModel CopyCommand
        {
            get
            {
                if (_copyCommand == null)
                {
                    _copyCommand = new CommandViewModel(
                        Strings.SiteList_Copy_DisplayString,
                        Strings.SiteList_Copy_ToolTip,
                        new RelayCommand(
                            param => CopyCommandExecuted(),
                            param=>CopyCommandCanExecute()));
                }
                return _copyCommand;
            }
        } private CommandViewModel _copyCommand;

        /// <summary>
        /// Send keys command
        /// </summary>
        public CommandViewModel SendKeysCommand
        {
            get
            {
                if (_sendKeysCommand == null)
                {
                    _sendKeysCommand = new CommandViewModel(
                        Strings.SiteList_SendKeys_DisplayString,
                        Strings.SiteList_SendKeys_ToolTip,
                        new RelayCommand(
                            param => SendKeysCommandExecuted(),
                            param => SendKeysCommandCanExecute()));
                }
                return _sendKeysCommand;
            }
        } private CommandViewModel _sendKeysCommand;

        /// <summary>
        /// Delete command
        /// </summary>
        public CommandViewModel DeleteCommand
        {
            get
            {
                if (_deleteCommand == null)
                {
                    _deleteCommand = new CommandViewModel(
                        Strings.SiteList_Delete_DisplayString,
                        Strings.SiteList_DeleteTag_ToolTip,
                        new RelayCommand(
                            param => DeleteCommandExecuted(),
                            param => DeleteCommandCanExecute()));
                }
                return _deleteCommand;
            }
        } private CommandViewModel _deleteCommand;

        /// <summary>
        /// Delete Tag command
        /// </summary>
        public CommandViewModel DeleteTagCommand
        {
            get
            {
                if (_deleteTagCommand == null)
                {
                    _deleteTagCommand = new CommandViewModel(
                        Strings.SiteList_DeleteTag_DisplayString,
                        Strings.SiteList_DeleteTag_ToolTip,
                        new RelayCommand(
                            param => DeleteTagCommandExecuted(param),
                            param => DeleteTagCommandCanExecute()));
                    SetDeleteTagCommandVisibility();                    
                }
                return _deleteTagCommand;
            }
        } private CommandViewModel _deleteTagCommand;

        /// <summary>
        /// Delete Tag command
        /// </summary>
        public CommandViewModel DropDataCommand
        {
            get
            {
                if (_dropCommand == null)
                {
                    _dropCommand = new CommandViewModel(null, new RelayCommand(param => DropDataCommandExecuted(param)));                   
                }
                return _dropCommand;
            }
        } private CommandViewModel _dropCommand;

        public CommandViewModel CanDropDataCommand
        {
            get
            {
                if (_canDropDataCommand == null)
                {
                    _canDropDataCommand = new CommandViewModel(
                        null, 
                        new RelayCommand(param => CanDropDataCommandExecuted(param)));
                }
                return _canDropDataCommand;
            }
        } private CommandViewModel _canDropDataCommand;

        public CommandViewModel PasteCommand
        {
            get
            {
                if(_pasteCommand == null)
                {
                    _pasteCommand = new CommandViewModel(
                        Strings.SitesList_Paste_DisplayName,
                        Strings.SitesList_Paste_ToolTip,
                        new RelayCommand(
                            param => PasteCommandExecuted(), 
                            param => PasteCommandCanExecute()));

                }
                return _pasteCommand;
            }
        } private CommandViewModel _pasteCommand;

        #endregion // Public Properties

        #region Command Handlers 

        private bool ShowSitePropertiesCommandCanExecute()
        {
            return _collectionViewSource.View.CurrentPosition >= 0;
        }
        private bool CopyCommandCanExecute()
        {
            return _collectionViewSource.View.CurrentPosition >= 0;
        }
        private bool SendKeysCommandCanExecute()
        {
            return _collectionViewSource.View.CurrentPosition >= 0;
        }
        private bool DeleteCommandCanExecute()
        {
            return _collectionViewSource.View.CurrentPosition >= 0;
        }
        private bool DeleteTagCommandCanExecute()
        {
            return !String.IsNullOrEmpty(CurrentGroupName);
        }
        private static bool PasteCommandCanExecute()
        {
            return new IDataObjectHelper(Clipboard.GetDataObject()).HasUrl;
        }

        private void ShowSitePropertiesCommandExecuted()
        {
            SiteListItemViewModel current = _collectionViewSource.View.CurrentItem as SiteListItemViewModel;
            if (current == null)
                return;

            // Relay the command to the current site list item view model            
            current.ShowSitePropertiesCommand.Command.Execute(this);
        }

        private static void CreateNewLogOnSiteCommandExecuted()
        {
            // Navigate to the site details view passing a new login site as parameter.
            SiteDetailsViewModel vm = (SiteDetailsViewModel)NavigationManager.GoToSiteDetailsView(new LogOnSite(), true);
            vm.ChangesCommitted += delegate
            {
                PasswordProviderData.Current.Sites.Add(vm.Site);
            };
        }
        private static void CreateNewCreditCardCommandExecuted()
        {
            // Navigate to the site details view passing a new credit card site as parameter.
            SiteDetailsViewModel vm = (SiteDetailsViewModel)NavigationManager.GoToSiteDetailsView(new CreditCardSite(), true);
            vm.ChangesCommitted += delegate
            {
                PasswordProviderData.Current.Sites.Add(vm.Site);
            };
        }
        private static void CreateNewGenericSiteCommandExecuted()
        {
            SiteDetailsViewModel vm = (SiteDetailsViewModel)NavigationManager.GoToSiteDetailsView(new GenericSite(), true);
            vm.ChangesCommitted += delegate
            {
                PasswordProviderData.Current.Sites.Add(vm.Site);
            };
        }

        /// <summary>
        /// Copy command executed
        /// </summary>
        private void CopyCommandExecuted()
        {
            SiteListItemViewModel current = _collectionViewSource.View.CurrentItem as SiteListItemViewModel;
            if (current == null)
                return;

            if (!EnsureIsAuthenticatedForRestrictedDataAccess((Action)CopyCommandExecuted))
                return;

            // Get the site secret.
            EncryptedValue encrypted = PasswordProviderData.Current.Sites[current.Guid].PrimaryEncryptedValue;            
            if (encrypted != null)
            {
                // get string and copy to clip board.
                ProtectedString protectedValue = PasswordProviderData.Current.SecurityManager.GetValueDecrypted(encrypted);
                string value = protectedValue.UnprotectedString;
                Clipboard.SetText(value);
            }      
        }

        /// <summary>
        /// Delete command executed
        /// </summary>
        private void DeleteCommandExecuted()
        {
            SiteListItemViewModel current = _collectionViewSource.View.CurrentItem as SiteListItemViewModel;
            if (current == null)
                return;

            if (!EnsureIsAuthenticatedForRestrictedDataAccess((Action)DeleteCommandExecuted))
                return;
        
            bool result = DialogViewService.ShowMessage(this,
                String.Format(CultureInfo.CurrentCulture,
                    Strings.SiteList_DeleteSiteConfirmation, current.DisplayName),
                    Strings.SiteList_Delete_DisplayString,
                    MessageBoxButton.YesNo);
            if (result)
            {
                PasswordProviderData.Current.Sites.Remove(current.Guid);
            }
        
        }
        /// <summary>
        /// Delete Tag command executed.
        /// </summary>
        private void DeleteTagCommandExecuted(object param)
        {
            string tag = CurrentGroupName;
            if (!String.IsNullOrEmpty(tag))
            {
                if (!EnsureIsAuthenticatedForRestrictedDataAccess((Action<object>)DeleteTagCommandExecuted, param))
                    return;

                bool result = DialogViewService.ShowMessage(this,
                    String.Format(CultureInfo.CurrentCulture, Strings.SiteList_DeleteTagSiteConfirmation, tag),
                    Strings.SiteList_DeleteTag_DisplayString,                    
                    MessageBoxButton.YesNo);
                if (result)
                {
                    PasswordProviderData.Current.Sites.DeleteTag(tag);
                    _collectionViewSource.View.Refresh();
                }
            }
        }        
        /// <summary>
        /// SendKeys command executed
        /// </summary>
        private void SendKeysCommandExecuted()
        {
            SiteListItemViewModel current = _collectionViewSource.View.CurrentItem as SiteListItemViewModel;
            if (current == null)
                return;

            if (!EnsureIsAuthenticatedForRestrictedDataAccess((Action)SendKeysCommandExecuted))
                return;

            bool result = DialogViewService.ShowMessage(this,
                Strings.SiteList_SendKeys_HelpMessage, Strings.SiteList_SendKeys_DisplayString,
                MessageBoxButton.OKCancel);

            if (result)
            {
                Application.Current.Deactivated += new EventHandler(HandleSendKeysWhenWindowDeactivated);
                IsEnabled = false;
            }
        }

        /// <summary>
        /// Create a new site with url if the drop data contains url information.
        /// </summary>
        private static void DropDataCommandExecuted(object param)
        {
            DragEventArgs args = param as DragEventArgs;
            IDataObjectHelper helper = new IDataObjectHelper(args.Data);
            if (helper.HasUrl)
            {
                LogOnSite site = new LogOnSite();
                site.Url = helper.Url;
                site.IconUrl = helper.IconUrl;
                site.Name = helper.Title;
                NavigationManager.GoToSiteDetailsView(site, true);

                args.Handled = true;
            }
        }

        /// <summary>
        /// Report back to the UI whether or not the data is valid for drop.
        /// </summary>
        private static void CanDropDataCommandExecuted(object param)
        {
            DragEventArgs args = param as DragEventArgs;

            // Can drop if there is a uri.
            if (new IDataObjectHelper(args.Data).HasUrl)
            {
                args.Effects = DragDropEffects.Copy;
                args.Handled = true;
            }
            else
            {
                args.Effects = DragDropEffects.None;
                args.Handled = true;
            }
        }
        /// <summary>
        /// Respond to Paste, use data in clipboard to create new site.
        /// </summary>
        private static void PasteCommandExecuted()
        {
            IDataObjectHelper helper = new IDataObjectHelper(Clipboard.GetDataObject());
            if (helper.HasUrl)
            {
                LogOnSite site = new LogOnSite();
                site.Url = helper.Url;
                site.IconUrl = helper.IconUrl;
                site.Name = helper.Title;
                NavigationManager.GoToSiteDetailsView(site, true);
            }
        }

        #endregion
       
        #region Private Helpers


        void PasswordProviderData_CurrentChanged(object sender, EventArgs e)
        {
            // Force a re-load of the collection view.
            _collectionViewSource = null;
            OnPropertyChanged("ItemsSource");
        }


        /// <summary>
        /// Create the collection view source.
        /// </summary>
        private CollectionViewSource CreateCollectionViewSource()
        {
            CollectionViewSource vs = new CollectionViewSource();

            vs.Source = CreateCollectionViewData();
            SetSortDescriptions(vs);
            SetGroupDescriptions(vs);

            if (Settings.Default.ListViewGroupMethod == ListViewGroupMethod.NoGrouping)
                vs.View.MoveCurrentToFirst();
            else
                vs.View.MoveCurrentTo(null);

            return vs;
        }

        // If IsAuthenticated returns true, this method returns true without taking
        // any more action. If false, however, NavigationManager.GoToEnterPasswordView method is called
        // with a callback delegate that calls parameter "Action method" after successful authentication.
        // Since navigation is asychronous, this method returns before authentication has been carried out,
        // the return value will be false. 
        private static bool EnsureIsAuthenticatedForRestrictedDataAccess(Delegate callback, params object[] args)
        {
            // Get authenticated only if needed.
            if (PasswordProviderData.Current.SecurityManager.IsAuthenticated)
                return true;

            NavigationManager.GoToEnterPasswordView(true,
                (d) =>
                {
                    if (PasswordProviderData.Current.SecurityManager.Authenticate(d))
                    {
                        if (callback != null)
                            callback.DynamicInvoke(args);
                        return true;
                    }
                    return false;
                });

            return false;
        }


        // The send keys command waits until the window is deactivated, in response to the user
        // activating the desired destination window. This is the event handler for the window
        // Deactivated event. Password key strokes are sent to the active window using the SendKeys command.
        void HandleSendKeysWhenWindowDeactivated(object sender, EventArgs e)
        {
            Application.Current.Deactivated -= new EventHandler(HandleSendKeysWhenWindowDeactivated);
            IsEnabled = true;

            SiteListItemViewModel current = _collectionViewSource.View.CurrentItem as SiteListItemViewModel;
            if (current == null)
                return;

            EncryptedValue encryptedValue = PasswordProviderData.Current.Sites[current.Guid].PrimaryEncryptedValue;
            ProtectedString protectedValue = PasswordProviderData.Current.SecurityManager.GetValueDecrypted(encryptedValue);
            if (protectedValue == null)
                return;

            // For SendKeys, some characters are special and must be enclosed in brackets.
            string stringValue = protectedValue.UnprotectedString;
            System.Text.StringBuilder builder = new System.Text.StringBuilder();
            string specialChars = @"+^%~()[]{}";
            foreach (char c in stringValue.ToCharArray())
            {
                if (specialChars.Contains(c.ToString()))
                {
                    builder.Append('{');
                    builder.Append(c);
                    builder.Append('}');
                }
                else
                {
                    builder.Append(c);
                }
            }

            System.Threading.Thread.Sleep(1000); // Give the target window time to make it to the top of the Z order.
            System.Windows.Forms.SendKeys.SendWait(builder.ToString());
        }

        /// <summary>
        /// Handle settings value changes.
        /// </summary>
        void Settings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "ListViewSortMethod":
                    SetSortDescriptions(_collectionViewSource);
                    break;
                case "ListViewGroupMethod":
                    SetGroupDescriptions(_collectionViewSource);
                    SetDeleteTagCommandVisibility();
                    break;                
            }
        }

        /// <summary>
        /// Sort the items in the collection view source based on setting value.
        /// </summary>
        private static void SetSortDescriptions(CollectionViewSource src)
        {
            if (src == null)
                throw new ArgumentNullException("src");

            // Sort...
            switch (Settings.Default.ListViewSortMethod)
            {
                case ListViewSortMethod.AlphabeticallyByName:
                    src.SortDescriptions.Insert(0,
                        new SortDescription("DisplayName", ListSortDirection.Ascending));
                    break;
                case ListViewSortMethod.ByAccessCount:
                    src.SortDescriptions.Insert(0,
                        new SortDescription("AccessCount", ListSortDirection.Descending));
                    break;
                case ListViewSortMethod.ByLastAccessDate:
                    src.SortDescriptions.Insert(0,
                        new SortDescription("LastAccessDate", ListSortDirection.Descending));
                    break;
                default:
                    throw new InvalidOperationException();
            }
            // Don't allow the collection to grow...
            if (src.SortDescriptions.Count > 3)
                src.SortDescriptions.RemoveAt(3);
        }

        /// <summary>
        /// Set the group descriptions on the collection view source, based on user settings.
        /// </summary>
        private static void SetGroupDescriptions(CollectionViewSource src)
        {
            src.GroupDescriptions.Clear();
            switch (Settings.Default.ListViewGroupMethod)
            {
                case ListViewGroupMethod.NoGrouping:
                    src.SortDescriptions.Remove(new SortDescription("ComparativelyLowTag", ListSortDirection.Ascending));
                    // do nothing.
                    break;
                case ListViewGroupMethod.GroupByType:
                    src.SortDescriptions.Remove(new SortDescription("ComparativelyLowTag", ListSortDirection.Ascending));
                    src.GroupDescriptions.Add(new PropertyGroupDescription("GroupDisplayName"));
                    break;
                case ListViewGroupMethod.GroupByTag:
                    src.GroupDescriptions.Add(new PropertyGroupDescription("Tags", new EmptyTagToGroupValueConverter(), StringComparison.OrdinalIgnoreCase));
                    src.SortDescriptions.Insert(0, new SortDescription("ComparativelyLowTag", ListSortDirection.Ascending));
                    
                    //src.View.Custom

                    break;
            }
        }
        
        /// <summary>
        /// Create the observable collection of SiteListViewItemViewModel items for the list box.
        /// </summary>
        private ObservableCollection<SiteListItemViewModel> CreateCollectionViewData()
        {            
            // Clear the event handler while the list is being generated.
            SiteListItemViewModel.TagsChanged -= new EventHandler(SiteListItemViewModel_TagsChanged);

            ObservableCollection<SiteListItemViewModel> list =
                new ObservableCollection<SiteListItemViewModel>();


            foreach (Site site in PasswordProviderData.Current.Sites)
            {
                list.Add(SiteListItemViewModel.Create(site));                
            }

            // Watch the collection of sites for changes, and update the collection view in response.
            (PasswordProviderData.Current.Sites as INotifyCollectionChanged).CollectionChanged += 
                new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Sites_CollectionChanged);

            // Watch the site list item view model static event for notification that the tags have
            // changed, affecting the display of groups.
            SiteListItemViewModel.TagsChanged += new EventHandler(SiteListItemViewModel_TagsChanged);

            return list;
        }

        /// <summary>
        /// Force a refresh
        /// </summary>
        void SiteListItemViewModel_TagsChanged(object sender, EventArgs e)
        {
            this.ItemsSource.Refresh();
        }

        /// <summary>
        /// Event handler for the Sites collection changed event.
        /// </summary>
        void Sites_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
                    Guid siteGuid = (e.OldItems[0] as Site).Guid;
                    foreach (SiteListItemViewModel vm in CollectionSource)
                    {
                        if (vm.Guid == siteGuid)
                        {
                            CollectionSource.Remove(vm);
                            break;
                        }
                    }
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
                    SiteListItemViewModel item = SiteListItemViewModel.Create(e.NewItems[0] as Site);                    
                    CollectionSource.Add(item);
                    item.IsSelected = true;
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
                    _collectionViewSource.Source = CreateCollectionViewData();
                    break;
                default:
                    throw new InvalidOperationException(e.Action.ToString());

            }
        }

        /// <summary>
        /// Get the collection view source as an observable collection.
        /// </summary>
        private ObservableCollection<SiteListItemViewModel> CollectionSource
        {
            get 
            { 
                if(_collectionViewSource != null)
                    return _collectionViewSource.Source as ObservableCollection<SiteListItemViewModel>;
                return null;
            }
        }
        
        /// <summary>
        /// Determine the visibility of the Delete Tag command, depends on whether display is grouped by tags.
        /// </summary>
        private void SetDeleteTagCommandVisibility()
        {

            DeleteTagCommand.Visibility = (Settings.Default.ListViewGroupMethod == ListViewGroupMethod.GroupByTag) ?
                    Visibility.Visible : Visibility.Collapsed;
            
        }

        #endregion // Private Helpers
    }

    /// <summary>
    /// Given a list of tags which represent the groups an item belongs to, if the list is empty,
    /// return an empty string so the item will appear in the group of items that have no tag.
    /// </summary>
    class EmptyTagToGroupValueConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {            
            Collection<string> list = value as Collection<string>;
            if (list != null)
            {
                if (list.Count == 0)
                    return "(no tag)";
                else
                    return list;
            }

            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
